-module(rumFormatTer).

-include("rumDef.hrl").

-compile(inline).
-compile({inline_size, 128}).

-export([
   format/2
   , format/3
   , formatCfg/1
]).

%%
%% API Functions
%%

%% @doc Provides a generic, default formatting for log messages using a semi-iolist as configuration.  Any iolist allowed
%% elements in the configuration are printed verbatim.  Atoms in the configuration are treated as metadata properties
%% and extracted from the log message.  Optionally, a tuple of {atom(),semi-iolist()} can be used.  The atom will look
%% up the property, but if not found it will use the semi-iolist() instead.  These fallbacks can be similarly nested
%% or refer to other properties, if desired. You can also use a {atom, semi-iolist(), semi-iolist()} formatTer, which
%% acts like a ternary operator's true/false branches.
%%
%% The metadata properties datetime, message, severity, pid, line, module, and function  will always exist.

%% @doc使用半ioiolist作为配置为日志消息提供通用的默认格式。允许的任何iolist配置中的
%%元素按原样打印。配置中的原子被视为元数据属性
%%并从日志消息中提取。可选地，可以使用{atom（），semi-iolist（）}的元组。原子会看起来
%%的属性，但如果找不到，它将使用semi-iolist（）代替。这些后备广告可以类似地嵌套
%%或参考其他属性（如果需要）。您还可以使用{atom，semi-iolist（），semi-iolist（）} formatTer，
%%的作用类似于三元运算符的true / false分支。
%%
%%元数据属性日期，时间，消息，严重性和严重性将始终存在。
%%如果使用解析器转换，则pid，文件，行，模块和函数的属性将始终存在

%%
%% Example:
%%
%%    `["Foo"]' -> "Foo", regardless of message content.
%%
%%    `[message]' -> The content of the logged message, alone.
%%
%%    `[{pid,"Unknown Pid"}]' -> "?.?.?" if pid is in the metadata, "Unknown Pid" if not.
%%
%%    `[{pid, ["My pid is ", pid], ["Unknown Pid"]}]' -> if pid is in the metada print "My pid is ?.?.?", otherwise print "Unknown Pid"
%% @end

-spec format(rumMsg(), list()) -> any().
format(RumMsg, Config) ->
   [output(V, RumMsg) || V <- Config].

format(RumMsg, Config, Colors) ->
   [
      case V of
         color -> outColor(Colors, RumMsg);
         _ -> output(V, RumMsg)
      end || V <- Config
   ].

formatCfg([]) ->
   [datetime, color, <<"[">>, severity, <<"]">>, node, <<"|">>, pid, <<"|">>, module, <<"|">>, function, <<"|">>, line, <<"|">>, message, <<"\n">>];
formatCfg(MetaWhitelist) ->
   [datetime, color, <<"[">>, severity, <<"]">>, node, <<"|">>, pid, <<"|">>, module, <<"|">>, function, <<"|">>, line, <<"|">>] ++
      [{M, [atom_to_binary(M), <<"=">>, M, "|"], [<<>>]} || M <- MetaWhitelist] ++ [message, <<"\n">>].

% Level, Pid, Node, Module, Function, FunctionArity, Line

-define(FixMd, [pid, node, module, function, line]).

-spec output(term(), rumMsg()) -> iolist().
output(message, RumMsg) -> RumMsg#rumMsg.message;
output(datetime, RumMsg) -> RumMsg#rumMsg.datetime;
output(pid, RumMsg) -> RumMsg#rumMsg.pid;
output(node, RumMsg) -> RumMsg#rumMsg.node;
output(datetime, RumMsg) -> RumMsg#rumMsg.datetime;
output(module, RumMsg) -> atom_to_binary(RumMsg#rumMsg.module, utf8);
output(function, RumMsg) -> atom_to_binary(RumMsg#rumMsg.function, utf8);
output(line, RumMsg) -> integer_to_binary(RumMsg#rumMsg.line);
output(severity, RumMsg) -> loSeverity(RumMsg#rumMsg.severity);
output(upSeverity, RumMsg) -> upSeverity(RumMsg#rumMsg.severity);
output(blank, _RumMsg) -> <<" ">>;
output(node, RumMsg) -> atom_to_binary(RumMsg#rumMsg.node, utf8);
output(sev, RumMsg) ->
   sevSeverity(RumMsg#rumMsg.severity); %% Write brief acronym for the severity level (e.g. debug -> $D)
output(metadata, RumMsg) -> mdJoin(RumMsg#rumMsg.metadata, <<"|">>, <<>>);
output({blank, Fill}, _RumMsg) -> Fill;
output({metadata, IntSep, FieldSep}, RumMsg) ->
   MD = lists:keysort(1, RumMsg#rumMsg.metadata), mdJoin(MD, IntSep, FieldSep, <<>>);
output({pterm, Key}, _RumMsg) ->
   makeStr(getPTerm(Key, <<"">>));
output({pterm, Key, Default}, _RumMsg) ->
   makeStr(getPTerm(Key, Default));
output(Prop, RumMsg) when is_atom(Prop) ->
   makeStr(getMdKey(Prop, RumMsg, <<"Undefined">>));
output({Prop, Default}, RumMsg) when is_atom(Prop) ->
   makeStr(getMdKey(Prop, RumMsg, output(Default, RumMsg)));
output({Prop, Present, Absent}, RumMsg) when is_atom(Prop) ->
   case getMdKey(Prop, RumMsg) of
      undefined ->
         [output(V, RumMsg) || V <- Absent];
      _ ->
         [output(V, RumMsg) || V <- Present]
   end;
output({Prop, Present, Absent, Width}, RumMsg) when is_atom(Prop) ->
   %% sort of like a poor man's ternary operator
   case getMdKey(Prop, RumMsg) of
      undefined ->
         [output(V, RumMsg, Width) || V <- Absent];
      _ ->
         [output(V, RumMsg, Width) || V <- Present]
   end;
output(Other, _) -> makeStr(Other).

output(message, RumMsg, _Width) -> RumMsg#rumMsg.message;
output(datetime, RumMsg, _Width) -> RumMsg#rumMsg.datetime;
output(pid, RumMsg, _Width) -> RumMsg#rumMsg.pid;
output(node, RumMsg, _Width) -> RumMsg#rumMsg.node;
output(module, RumMsg, _Width) -> atom_to_binary(RumMsg#rumMsg.module, utf8);
output(function, RumMsg, _Width) -> atom_to_binary(RumMsg#rumMsg.function, utf8);
output(line, RumMsg, _Width) -> integer_to_binary(RumMsg#rumMsg.line);
output(severity, RumMsg, _Width) -> loSeverity(RumMsg#rumMsg.severity);
output(upSeverity, RumMsg, _Width) -> upSeverity(RumMsg#rumMsg.severity);
output(blank, _RumMsg, _Width) -> <<" ">>;
output(node, RumMsg, _Width) -> atom_to_binary(RumMsg#rumMsg.node, utf8);
output(sev, RumMsg, _Width) ->
   sevSeverity(RumMsg#rumMsg.severity); %% Write brief acronym for the severity level (e.g. debug -> $D)
output({blank, Fill}, _RumMsg, _Width) -> Fill;
output(metadata, RumMsg, _Width) -> mdJoin(RumMsg#rumMsg.metadata, <<"|">>, <<>>);
output({metadata, IntSep, FieldSep}, RumMsg, _Width) ->
   MD = lists:keysort(1, RumMsg#rumMsg.metadata), mdJoin(MD, IntSep, FieldSep, <<>>);
output({pterm, Key}, _RumMsg, _Width) -> makeStr(getPTerm(Key, <<"">>));
output({pterm, Key, Default}, _RumMsg, _Width) -> makeStr(getPTerm(Key, Default));
output(Prop, RumMsg, Width) when is_atom(Prop) ->
   makeStr(getMdKey(Prop, RumMsg, <<"Undefined">>), Width);
output({Prop, Default}, RumMsg, Width) when is_atom(Prop) ->
   makeStr(getMdKey(Prop, RumMsg, output(Default, RumMsg)), Width);
output(Other, _, Width) -> makeStr(Other, Width).

outColor([], _RumMsg) -> <<>>;
outColor(Colors, RumMsg) ->
   case lists:keyfind(RumMsg#rumMsg.severity, 1, Colors) of
      {_, Color} -> Color;
      _ -> <<>>
   end.

-spec makeStr(any()) -> iolist().
makeStr(A) when is_atom(A) -> atom_to_binary(A);
makeStr(P) when is_pid(P) -> list_to_binary(pid_to_list(P));
makeStr(B) when is_binary(B) -> B;
makeStr(Other) -> eFmt:formatBin("~p", [Other]).

makeList(A) when is_atom(A) -> atom_to_list(A);
makeList(P) when is_pid(P) -> pid_to_list(P);
makeList(Other) -> binary_to_list(eFmt:formatBin("~p", [Other])).

makeStr(A, W) when is_integer(W) -> makeStr(left, makeList(A), W);
makeStr(A, {Align, W}) when is_integer(W) ->
   makeStr(Align, makeList(A), W);
makeStr(A, _W) -> makeStr(A).

makeStr(left, Str, W) ->
   list_to_binary(string:left(Str, W));
makeStr(centre, Str, W) ->
   list_to_binary(string:centre(Str, W));
makeStr(right, Str, W) ->
   list_to_binary(string:right(Str, W));
makeStr(_, Str, W) ->
   list_to_binary(string:left(Str, W)).

%% persistent term was introduced in OTP 21.2, so
%% if we're running on an older OTP, just return the
%% default value.
getPTerm(Key, Default) ->
   persistent_term:get(Key, Default).

getMdKey(Key, RumMsg) ->
   getMdKey(Key, RumMsg, undefined).

getMdKey(Key, RumMsg, Default) ->
   case lists:keyfind(Key, 1, RumMsg#rumMsg.metadata) of
      false ->
         case lists:member(Key, ?FixMd) of
            true ->
               output(Key, RumMsg);
            _ ->
               Default
         end;
      {_Key, Fun} when is_function(Fun) ->
         runFun(Fun, Default);
      {_Key, Value} ->
         Value
   end.

runFun(Fun, Default) ->
   try Fun()
   catch
      _:_ ->
         Default
   end.

loSeverity(?debug) -> <<"debug">>;
loSeverity(?info) -> <<"info">>;
loSeverity(?notice) -> <<"notice">>;
loSeverity(?warning) -> <<"warning">>;
loSeverity(?error) -> <<"error">>;
loSeverity(?critical) -> <<"critical">>;
loSeverity(?alert) -> <<"alert">>;
loSeverity(?emergency) -> <<"emergency">>;
loSeverity(?none) -> <<"none">>.

upSeverity(?debug) -> <<"DEBUG">>;
upSeverity(?info) -> <<"INFO">>;
upSeverity(?notice) -> <<"NOTICE">>;
upSeverity(?warning) -> <<"WARNING">>;
upSeverity(?error) -> <<"ERROR">>;
upSeverity(?critical) -> <<"CRITICAL">>;
upSeverity(?alert) -> <<"ALERT">>;
upSeverity(?emergency) -> <<"EMERGENCY">>;
upSeverity(?none) -> <<"NONE">>.

sevSeverity(?debug) -> <<"[D]">>;
sevSeverity(?info) -> <<"[I]">>;
sevSeverity(?notice) -> <<"[N]">>;
sevSeverity(?warning) -> <<"[W]">>;
sevSeverity(?error) -> <<"[E]">>;
sevSeverity(?critical) -> <<"[C]">>;
sevSeverity(?alert) -> <<"[A]">>;
sevSeverity(?emergency) -> <<"[E]">>;
sevSeverity(?none) -> <<"[o]">>.

mdJoin([], _FieldSep, BinAcc) ->
   BinAcc;
mdJoin([{_K, V}], _FieldSep, BinAcc) ->
   <<BinAcc/binary, (eFmt:formatBin("~p", [V]))/binary>>;
mdJoin([{_K, V} | Left], FieldSep, BinAcc) ->
   mdJoin(Left, FieldSep, <<BinAcc/binary, (eFmt:formatBin("~p", [V]))/binary, FieldSep/binary>>).

mdJoin([], _IntSep, _FieldSep, BinAcc) ->
   BinAcc;
mdJoin([{K, V}], IntSep, _FieldSep, BinAcc) ->
   <<BinAcc/binary, (eFmt:formatBin("~p~s~p", [K, IntSep, V]))/binary>>;
mdJoin([{K, V} | Left], IntSep, FieldSep, BinAcc) ->
   mdJoin(Left, FieldSep, <<BinAcc/binary, (eFmt:formatBin("~p~s~p", [K, IntSep, V]))/binary, FieldSep/binary>>).





